【小ネタ】Windowsでcfn-signalを使用する時の注意
ウィスキー、シガー、パイプをこよなく愛する大栗です。
Windows ServerのEC2インスタンスをCloudFormationで構成していたのですが、少し嵌った事があったので注意点をまとめてみます。EC2のOSはAmazon Linuxを使用することが多いのですが、Windowsの場合に異なる部分がありました。
CloudFormationでEC2を作成する時のポイント
CloudFormationでEC2インスタンスを構成するときに、UserDataやCloudFormation::Initを使用してミドルウェアをインストールすることで一発で環境を構成することができます。しかし、ここで問題になるのがリソースの作成順序です。
CloudFormationでEC2インスタンスを作成するときにはステータスがrunning
になるとリソースとしてはCREATE_COMPLETE
になり、作成したEC2に依存しているリソースの作成処理が流れてしまいます。するとUserDataでミドルウェアをインストールする前に後続処理が走ってしまい都合が悪くなります。例えばWebサーバをインストールする前にELBに登録されてしまい、Unhealthyになりなかなかつながらない場合などがあります。
この場合は、WaitConditionとWaitConditionHandleを利用します。UserDataの処理の最後でcfn-signal
コマンドを実行してWaitConditionHandleへシグナルを送ります。WaitConditionHandleにシグナルが送られるとWaitConditionがCREATE_COMPLETE
となります。つまり、UserDataの実行後にリソースを作成したい場合には、作成したいリソースのDependsOn
属性でWaitConditionを指定すれば良いのです。
嵌ったこと
Windowsでcfn-signal
コマンドを使用した時に問題が発生しました。
cfn-signal
コマンドはドキュメントによると以下の様な形式でWaitConditionHandleへシグナルを送ります。
cfn-signal --success|-s signal.to.send \ --reason|-r resource.status.reason \ --data|-d data \ --id|-i unique.id \ --exit-code|-e exit.code \ waitconditionhandle.url
Linuxの場合
Amazon Linux(ami-374db956)で起動するときには、以下の様なテンプレートで起動できます。cfn-signal
コマンドは以下のようになります。
"/opt/aws/bin/cfn-signal -e $? -r \"AppServer01 setup complete\" '", { "Ref" : "Srv01WaitHandle" }, "'"
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Linux Stack", "Resources" : { "Srv01WaitHandle" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" }, "Srv01WaitCondition" : { "Type" : "AWS::CloudFormation::WaitCondition", "Properties" : { "Handle" : {"Ref" : "Srv01WaitHandle"}, "Timeout" : "900" } }, "Server01": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-374db956", "InstanceType": "c4.large", "KeyName": "KeyPairName", "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "#! /bin/bash -v", "\n", "sleep 180", "\n", "# All is well so signal success", "\n", "/opt/aws/bin/cfn-signal -e $? -r \"AppServer01 setup complete\" '", { "Ref" : "Srv01WaitHandle" }, "'" ]]}} } } } }
EC2インスタンスが起動してから180秒以上経ってからWaitConditionがCREATE_COMPLETE
になります。
Windowsの場合
Windows Server 2012 R2(ami-0f19db6e)で、ImageIdを変更してUserDataを以下のようにcfn-signal
コマンドを書き換えてテンプレートを実行してみました。
"<script>", "\n", "powershell.exe -command Start-Sleep -s 180", "\n", "", "\n", "cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Ref" : "Srv01WaitHandle" }, "\"", "\n", "</script>"
しかし、WaitConditionがCREATE_COMPLETE
にならずに、そのままタイムアウトしてCREATE_FAILED
になってしまいます。
Windowsの正しいcfn-signalの呼び方
正しい呼び方は、『AWS CloudFormation の Windows スタックのブートストラップ』というドキュメントに記載がありました。
Windows スタックでは、待機条件ハンドル URL を再度 base64 エンコードする必要があります。
つまりFn::Base64
関数を使えということでした。書き換えたテンプレートの全体は、以下のようになります。
cfn-signalの部分は"cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Fn::Base64" : { "Ref" : "Srv01WaitHandle" }}, "\"", "\n"
となります
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Linux Stack", "Resources" : { "Srv01WaitHandle" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" }, "Srv01WaitCondition" : { "Type" : "AWS::CloudFormation::WaitCondition", "Properties" : { "Handle" : {"Ref" : "Srv01WaitHandle"}, "Timeout" : "900" } }, "Server01": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-0f19db6e", "InstanceType": "c4.large", "KeyName": "KeyPairName", "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "<script>", "\n", "powershell.exe -command Start-Sleep -s 180", "\n", "", "\n", "cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Fn::Base64" : { "Ref" : "Srv01WaitHandle" }}, "\"", "\n", "</script>" ]]}} } } } }
上記のテンプレートでは問題なくWaitConditionがCREATE_COMPLETE
となりました。
さいごに
正直cfn-signal
の仕様をLinux版とWindows版で合せて欲しい部分でしたが、LinuxとWindowsではCloudFormationの書き方に細かい違いがあるので、注意して作成する必要があります。